/**
 * Copyright (C) 2010 Cloudfarming <info@cloudfarming.nl>
 *
 * Licensed under the Eclipse Public License - v 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package nl.cloudfarming.sensor.cropcircle;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import nl.cloudfarming.eventbus.GuiEventKey;
import nl.cloudfarming.sensor.Receiver;
import nl.cloudfarming.sensor.ServiceLoaderReceiver;
import nl.cloudfarming.util.filecopier.Exception.DirectoryCreationException;
import nl.cloudfarming.util.filecopier.Exception.FileMoveException;
import nl.cloudfarming.util.filecopier.Exception.NotADirectoryException;
import nl.cloudfarming.util.filecopier.Exception.NotAFileException;
import java.util.Date;
import nl.cloudfarming.client.geoviewer.Layer;
import nl.cloudfarming.client.geoviewer.LayerEventKey;
import nl.cloudfarming.client.geoviewer.LayerProvider;
import nl.cloudfarming.client.sensor.SensorDataLayer;
import nl.cloudfarming.client.sensor.SensorDataLayerProvider;
import nl.cloudfarming.client.sensor.SensorService;
import nl.cloudfarming.client.sensor.UpdatedSensorDataEventProducer;
import nl.cloudfarming.client.warmup.CloudfarmingWarmup;
import nl.cloudfarming.eventbus.GuiEvent;
import nl.cloudfarming.sensor.FileReadException;
import nl.cloudfarming.sensor.ParseException;
import nl.cloudfarming.sensor.model.GeoSensorDataFileBO;
import nl.cloudfarming.sensor.model.SensorMetadataBO;
import nl.cloudfarming.sensor.model.impl.SensorServiceImpl;
import nl.cloudfarming.sensor.util.MD5Sum;
import nl.cloudfarming.util.filecopier.GenericFileCopier;
import nl.cloudfarming.util.NBPreferencesHolder;
import nl.cloudfarming.util.PreferenceEnum;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.progress.ProgressHandleFactory;
import org.openide.util.Cancellable;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.ServiceProvider;

public class CropCircleReceiver extends ServiceLoaderReceiver<Object, File> implements Receiver<File>, Runnable, Cancellable {

    private static Log log = LogFactory.getLog(CropCircleReceiver.class);
    private BufferedReader input;
    private SensorService sensorService = new SensorServiceImpl();
    private GenericFileCopier fileCopier = new GenericFileCopier();
    private ArrayList<File> processingList = new ArrayList<File>();
    private long progress;
    private long progressTotal;
    private RequestProcessor.Task task;
    private ProgressHandle progressHandle;
    private static final String PROPERTY_MONTH = "MM";
    private static final String PROPERTY_YEAR = "YY";
    private static final String PROPERTY_YEAR_INTERNATIONAL ="YYYY";
    private static final String PROPERTY_DAY = "DD";

    @Override
    public void start() {
        task = RequestProcessor.getDefault().post(this);
        Cancellable myCancellable = task;
        progressHandle =
                ProgressHandleFactory.createHandle("Processing sensor data", myCancellable);
        progressHandle.start(100);
    }

    @Override
    public void stop() {
        task.cancel();
    }

    @Override
    public boolean cancel() {
        return task.cancel();
    }

    /*
     * checks if file is a csv file.
     */
    @Override
    public boolean canReceive(File receiverPayload) {
        if (receiverPayload.getName().toLowerCase().endsWith(".csv")) {
            return true;
        }
        return false;
    }

    @Override
    public void receive(File receiverPayload) {
        processingList.add(receiverPayload);
    }

    @Override
    public boolean isReceiving() {
        return !processingList.isEmpty();
    }

    /*
     * @Return a number between 1 and 100 representing the percentage of completion
     */
    public int getProgress() {
        int progressPerc = Math.round(this.progress / (progressTotal / 100));
        //failsave set max 100
        progressPerc = (progressPerc>100)?100:progressPerc;
        // one percent
        return progressPerc;
    }

    public void addToProgress(long processed) {
        progress += processed;
    }

    public void addProgresToProgressHandle(int progress) {
        if (progressHandle != null) {
            progressHandle.progress(progress);
        }
    }

    public void finishProgressHandle() {
        if (progressHandle != null) {
            progressHandle.finish();
        }
    }

    public void clearProgress() {
        progress = 0;
    }

    @Override
    public void run() {

        // Clear existing progress
        clearProgress();

        // Determine total first
        progressTotal = 0;
        for (File f : processingList) {
            progressTotal += f.length();
        }

        // Process
        for (File f : processingList) {
            try {
                String checkSum = MD5Sum.getHex(MD5Sum.createChecksum(new FileInputStream(f)));
                String fileName = f.getName();
                GeoSensorDataFileBO geoSensorDataFileBO;
                if ((geoSensorDataFileBO = sensorService.findGeoSensorDataFileByNameChecksum(fileName, checkSum)) == null) {
                    SensorMetadataBO metadata = buildMetdataFromPreferences();
                    sensorService.persist(metadata);
                    Date runDate = makeDateFromFileName(f.getName());
                    geoSensorDataFileBO = new GeoSensorDataFileBO(fileName, checkSum, runDate, new Date(System.currentTimeMillis()), metadata);
                    sensorService.persist(geoSensorDataFileBO);
                    readAndDispatch(f, fileName, checkSum, geoSensorDataFileBO);
                } else {
                    readAndDispatch(f, fileName, checkSum, geoSensorDataFileBO);
                }
            } catch (MalformedFileFormatException ex) {
                log.error(ex.getMessage());
            } catch (EmptyFileFormatProperty ex) {
                log.error(ex.getMessage());
            } catch (FileReadException ex) {
                StringBuilder sb = new StringBuilder("Can't read file: ");
                sb.append(f.getName());
                log.error(sb, ex);
            } catch (NoSuchAlgorithmException ex) {
                StringBuilder sb = new StringBuilder("No such algorith while creating checksum for file: ");
                sb.append(f.getName());
                log.error(sb, ex);
            } catch (IOException ex) {
                StringBuilder sb = new StringBuilder("IO exception while reading file: ");
                sb.append(f.getName());
                log.error(sb,ex);
            } catch (java.text.ParseException ex) {
                StringBuilder sb = new StringBuilder("java.text.ParseException while trying to parse file: ");
                sb.append(f.getName());
                log.error(sb,ex);
            } catch (ParseException ex) {
                log.error(ex.getMessage());

            }
        }
        finishProgressHandle();
    }

    public void readAndDispatch(File receiverPayload, String fileName, String checkSum, GeoSensorDataFileBO geoSensorDataFileBO) throws NoSuchAlgorithmException, IOException, FileReadException {
        try {
            if (receiverPayload.canRead()) {
                input = new BufferedReader(new FileReader(receiverPayload));
                String line;

                while ((line = input.readLine()) != null) {
                    dispatchHandling(line, geoSensorDataFileBO);
                    addToProgress(line.getBytes().length);
                    addProgresToProgressHandle(getProgress());
                }
                //In dispatchandling groups are made. This is done to speed up performance this is the only place where 
                //we can retreive the end of file so we must send the remainder to the handler.
                sendRemainderToHandlers();
                //Sluit het bestand
                input.close();

                //Verplaats verwerkt bestand naar archive map
                //Get copy to directory from netbeans propperties
                NBPreferencesHolder preferencesHolder = new NBPreferencesHolder();
                String loadString =  preferencesHolder.getProperty(CloudfarmingWarmup.class, PreferenceEnum.ARCHIVE);

                fileCopier.moveFile((receiverPayload), new File(loadString), true);

                //FIXME hack to update layers loading of sensor data needs refactoring to simpler architecture
                for (LayerProvider provider : Lookup.getDefault().lookupAll(LayerProvider.class)){
                    if (provider instanceof SensorDataLayerProvider){
                        for (Layer layer: ((SensorDataLayerProvider)provider).getLookup().lookupAll(Layer.class)){
                            layer.updateLayer();
                        }
                    }
                }
            } else {
                throw new FileReadException("Exception while trying to read ");
            }
        } catch (NotADirectoryException ex) {
            StringBuilder sb = new StringBuilder("Not a directory exception while trying to archive file: ");
            sb.append((receiverPayload).getName());
            log.error(sb.toString(), ex);
        } catch (NotAFileException ex) {
            StringBuilder sb = new StringBuilder("Not a file exception while trying to archive file: ");
            sb.append((receiverPayload).getName());
            log.error(sb.toString(), ex);
        } catch (DirectoryCreationException ex) {
            StringBuilder sb = new StringBuilder("Directory creation exception while trying to archive file: ");
            sb.append((receiverPayload).getName());
            log.error(sb.toString(), ex);
        } catch (FileNotFoundException ex) {
            StringBuilder sb = new StringBuilder("File not found  exception while trying to archive file: ");
            sb.append((receiverPayload).getName());
            log.error(sb.toString(), ex);
        } catch (FileMoveException ex) {
            StringBuilder sb = new StringBuilder("FileMove  exception while trying to archive file: ");
            sb.append((receiverPayload).getName());
            log.error(sb.toString(), ex);
        }
    }

    public Date makeDateFromFileName(String fileName) throws ParseException, java.text.ParseException, EmptyFileFormatProperty, MalformedFileFormatException {
        NBPreferencesHolder nBPrefferncesHolder = new NBPreferencesHolder();
        String props = nBPrefferncesHolder.getProperty(CropCircleReceiver.class, PreferenceEnum.SENSORDATAFILEFORMAT);
        if (!props.isEmpty()) {
            if (props.contains(PROPERTY_DAY) && props.contains(PROPERTY_MONTH) && props.contains(PROPERTY_YEAR)) {
                int numberOfYearCharacters = 2;
                if(props.contains(PROPERTY_YEAR_INTERNATIONAL)){
                    numberOfYearCharacters = 4;
                }
                try {
                    String day = fileName.substring(props.indexOf(PROPERTY_DAY), props.indexOf(PROPERTY_DAY) + 2);
                    String month = fileName.substring(props.indexOf(PROPERTY_MONTH), props.indexOf(PROPERTY_MONTH) + 2);
                    String year = fileName.substring(props.indexOf(PROPERTY_YEAR), props.indexOf(PROPERTY_YEAR) + numberOfYearCharacters);
                    StringBuilder dateBuilder = new StringBuilder(day);
                    dateBuilder.append(month);
                    dateBuilder.append(year);

                    SimpleDateFormat date = new SimpleDateFormat("ddMMyy");
                    return date.parse(dateBuilder.toString());
                } catch (NumberFormatException e) {
                    StringBuilder sb = new StringBuilder("Could not convert filename: ");
                    sb.append(fileName);
                    sb.append("within current format propperties: ");
                    sb.append(props);
                    throw new ParseException(sb.toString(), e);
                } catch (java.text.ParseException e){
                    StringBuilder sb = new StringBuilder("Could not convert filename: ");
                    sb.append(fileName);
                    sb.append("within current format propperties: ");
                    sb.append(props);
                    throw new ParseException(sb.toString());
                }
            } else {
                StringBuilder sb = new StringBuilder("The property which specifies file format is malformed while creating date from filename: ");
                sb.append(fileName);
                throw new MalformedFileFormatException(sb.toString());
            }
        } else {
            StringBuilder sb = new StringBuilder("The property which specifies file format is not set while creating date from filename: ");
            sb.append(fileName);
            throw new EmptyFileFormatProperty(sb.toString());
        }
    }

    protected SensorMetadataBO buildMetdataFromPreferences(){
        NBPreferencesHolder holder = new NBPreferencesHolder();
        String fileFormat = holder.getProperty(this.getClass(), PreferenceEnum.SENSORDATAFILEFORMAT);
        int sensorHeigth = Integer.parseInt(holder.getProperty(this.getClass(), PreferenceEnum.CROPCIRCLESENSORHEIGTH));
        int numberOfSensors = Integer.parseInt(holder.getProperty(this.getClass(), PreferenceEnum.NUMBEROFCROPCIRCLESENSORS));
        String cropCircleSensorSpread = holder.getProperty(this.getClass(), PreferenceEnum.CROPCIRCLESENSORSPREAD);
        String sensorType = holder.getProperty(this.getClass(), PreferenceEnum.SENSORTYPE);
        String spectralChannels = holder.getProperty(this.getClass(), PreferenceEnum.SPECTRALCHANNELS);
        return new SensorMetadataBO(fileFormat, sensorHeigth , numberOfSensors, cropCircleSensorSpread, sensorType, spectralChannels);
    }

     class UpdatedLayerEvent implements GuiEvent<Layer>{

        private final Layer layer;

        public UpdatedLayerEvent(Layer layer) {
            this.layer = layer;
        }

        @Override
        public GuiEventKey getKey() {
            return LayerEventKey.DATA_CHANGED_EVENT;
        }

        @Override
        public Layer getContent() {
            return this.layer;
        }


    }
}
